package de.gwtprime.server.model;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import de.gwtprime.server.ChannelManager;
import de.gwtprime.server.Config;
import de.gwtprime.server.Config.Param;
import de.gwtprime.server.sql.helper.HibernateHelper;
import de.gwtprime.server.util.AppUtils;
import de.gwtprime.server.util.MailSender;
import de.gwtprime.shared.requestFactory.OrderProxy.OrderState;
import de.gwtprime.shared.requestFactory.OrderProxy.OrderType;
import de.gwtprime.shared.util.AppUtilsShared;

@Entity
@Table(name="trading_order")
public class Order extends EntityBase {



	public Order(){

	}
	/**
	 * Serial version ID
	 */
	private static final long serialVersionUID = 1L;

	private User2 user;
	
	private User2 buyer;
	
	private User2 seller;

	private String userName;

	private OrderType type;
	
	private Double amount;

	private Double price;

	private Date date;
	
	private OrderState state = OrderState.OPEN;
	
	private Double fee;
	
	private String privateWithdrawAddress;
	
	private Boolean processed = false;;

	public static Order findOrder(Long id) {
		return HibernateHelper.find(Order.class, id);
	}

	public static List<Order> loadOrders(){
		return HibernateHelper.queryDBForList(new Order(), 
				String.format("from %s where state = %s and amount > 0 and price > 0 order by price",
						Order.class.getSimpleName(),
						OrderState.OPEN.ordinal()
						));
	}

	/**
	 * Load completed trades (state 4 = completed, state 5 = completed and rated). Used for the price chart
	 * @return
	 */
	public static List<Order> loadOrdersCompleted(){
		return HibernateHelper.queryDBForList(new Order(), 
				String.format("from %s where state = %s and amount > 0 and price > 0 order by date", 
						Order.class.getSimpleName(), // from Order));
						OrderState.COMPLETE.ordinal())); 
	}
	
	/**
	 * Load completed trades (state 4 = completed, state 5 = completed and rated). Used for the price chart
	 * @return
	 */
	public static List<Order> loadUnprocessedOrders(){
		
		return HibernateHelper.queryDBForList(new Order(), 
				String.format("from %s where state = %s and amount > 0 and price > 0 and processed = %s order by date", 
						Order.class.getSimpleName(), // from Order
						OrderState.COMPLETE.ordinal(), // state = complete
						0 // processed = false
						)); 
	}
	
	public static List<Order> loadOrders(User2 user){
		
		User2 u = AppUtils.getUserFromSession();
		
		if(u==null)
			return new ArrayList<Order>();
		
		return HibernateHelper.queryDBForList(new Order(), 
				String.format("from Order where buyer.id="+u.getId()+" or seller.id="+u.getId()));
	}

	@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
	@JoinColumn
	public User2 getUser() {
		return user;
	}

	public void setUser(User2 user) {
		this.user = user;
		if(user!=null)
			this.setUserName(user.getEmail());
	}
	public OrderType getType() {
		return type;
	}



	public void setType(OrderType type) {
		this.type = type;
	}


	public Double getAmount() {
		return amount;
	}



	public void setAmount(Double amount) {
		this.amount = amount;
	}


	public Double getPrice() {
		return price;
	}



	public void setPrice(Double price) {
		this.price = price;
	}


	public String getUserName() {
		return userName;
	}



	public void setUserName(String userName) {
		this.userName = userName;
	}


	public Date getDate() {
		return date;
	}



	public void setDate(Date date) {
		this.date = date;
	}


	public static void addOrder(Order order){

		HibernateHelper.persist(order);

		
		
		HibernateHelper.persist(order);
		
	}
	

	@Override
	public Long persist() {

		EntityManager em = HibernateHelper.beginTransactionWithoutTransaction();
		try{

			if(user!=null)
				setUser((User2)user.mergePure(em));
			
			if(buyer!=null)
				setBuyer((User2)buyer.mergePure(em));
			
			if(seller!=null)
				setSeller((User2)seller.mergePure(em));

			if(getDate()==null)
				setDate(new Date());

			persistPure(em);
			//return super.persist();
			
			return this.getId();

		}finally{
			em.close();
		}
	}
	
	
	/**
	 * Create a new order
	 * @return
	 */
	public Long createNewOrder(){
		
		// State of the new order is always OPEN
		setState(OrderState.OPEN);
		
		// Store the current fee in the order (Fees could change during the life time of the order
		// and therefore needs to be stored for every order separately)
		setFee(Config.getInstance().getPropertyAsDouble(Param.tradingFee));
		
		User2 user = AppUtils.getUserFromSession();
		
		if(getType()==OrderType.SELL){
			setSeller(user);
			
			// Precheck
			if(getAmount() < AppUtilsShared.minTradingUnit || getAmount()>user.getBalanceBitcoin()){
				throw new IllegalArgumentException("Not enough Coins!");
			}
			
			seller.setBalanceBitcoin(seller.getBalanceBitcoin() - getAmount());
			seller.setBalanceBitcoinReserved(seller.getBalanceBitcoinReserved()+getAmount());
			
		}else{
			setBuyer(user);
		}
		
		Long id = persist();
		
		// Inform all clients about the new order
		ChannelManager.pushToAllClients("orderProcessed");
		return id;
	}



	public OrderState getState() {
		return state;
	}

	public void setState(OrderState state) {
		this.state = state;
	}

	@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
	@JoinColumn
	public User2 getBuyer() {
		return buyer;
	}

	public void setBuyer(User2 buyer) {
		this.buyer = buyer;
	}

	/**
	 * Process an trading order (BUY or SELL) of state OPEN.
	 * Set the new buyer or seller and inform the user about the processing of his order. 
	 * The new state is set to WAITING_FOR_PAYMENT. If the create of the order is the buyer,
	 * he needs to pay the seller now. If on the other hand is the seller, he just needs
	 * to wait for the payment now.
	 * @throws Exception An error is thrown, if the order is a buy order and the seller 
	 * does not have enough funds
	 */
	public void processOrder(){
		
		// Check if the order is the correct state
		if(getState()!=OrderState.OPEN){
			throw new IllegalArgumentException("Only trading orders in the state OPEN can be processed");
		}
		
		User2 u = AppUtils.getUserFromSession();
		
		// If we have a buy order -> we have found a seller.
		if(getType()==OrderType.BUY){
			
			// Don't allow seller to process order if he has not enough coins.
			// This should normally not happen because it should be already
			// fetched on the UI.
			if(u.getBalanceBitcoin()<getAmount()){
				throw new IllegalArgumentException("Seller does not have enough coins to "
						+ "process this order");
			}
			
			setSeller(u);
		}
		// If we have an sell order on the other hand -> we have found a buyer.
		else{
			setBuyer(u);
		}
		
		setState(OrderState.WAITING_FOR_PAYMENT);
		
		persist();
		
		ChannelManager.pushToAllClients("orderProcessed");
		
		if(getType()==OrderType.BUY) // If we have a buy order -> inform the buyer that he has found a seller
			MailSender.sendMail(getBuyer().getEmail(), "LTC verkauft", "LTC gekauft!");
		else // If we have a sell order -> inform the seller that he has found a buyer
			MailSender.sendMail(getSeller().getEmail(), "LTC verkauft", "LTC verkauft!");
		
	}

	
	@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
	@JoinColumn
	public User2 getSeller() {
		return seller;
	}

	public void setSeller(User2 seller) {
		this.seller = seller;
	}
	
	public void stornoTheOrder(){
		setState(OrderState.STORNIERT);
		super.persist();
	}
	
	public void paymentTriggered() {
		setState(OrderState.PAYMENT_TRIGGERED);
		super.persist();
		
		ChannelManager.pushToClient(getSeller(), "Payment triggered");
		// Inform the seller about the payment
		MailSender.sendMail(getSeller().getEmail(), "Zahlung angewiesen", "Zahlung wurde angewiesen.");
		
	}
	
	public void paymentReceived(){
		
		if(AppUtils.getUserFromSession()==null || !AppUtils.getUserFromSession().equals(getSeller())){
			logger.warning("The seller did not trigger this action");
			
			throw new IllegalArgumentException("not allowed!!");
		}
		
		setState(OrderState.COMPLETE);
		
		// Remove the reserved BTC from the seller
		getSeller().setBalanceBitcoinReserved(getSeller().getBalanceBitcoinReserved()-getAmount());
		
		// If buyer wants to get his coins to his private address
		if(!AppUtilsShared.isNullOrEmpty(getPrivateWithdrawAddress())){
			
		}else{
			setProcessed(true);
			// Add the BTC to the account of the buyer
			getBuyer().setBalanceBitcoin(getBuyer().getBalanceBitcoin()+getAmountMinusFee(getAmount(), getFee()));
			// Inform the seller about the payment
			MailSender.sendMail(getBuyer().getEmail(), "Zahlung erhalten - Transaktion abgeschlossen", "Zahlung erhalten - Transaktion abgeschlossen.");
		}
		
		super.persist();
		
		
	}

	public Double getFee() {
		return fee;
	}

	public void setFee(Double fee) {
		this.fee = fee;
	}

	public String getPrivateWithdrawAddress() {
		return privateWithdrawAddress;
	}

	public void setPrivateWithdrawAddress(String privateWithdrawAddress) {
		this.privateWithdrawAddress = privateWithdrawAddress;
	}

	public Boolean getProcessed() {
		return processed;
	}

	public void setProcessed(Boolean processed) {
		this.processed = processed;
	}
	
	public static double getAmountMinusFee(double amount, double fee){
		return amount*(100-fee)/100;
	}
}
